package com.flame.workflow.ru.engine.handler;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.flame.workflow.constants.RuntimeConstants;
import com.flame.workflow.constants.TaskConstants;
import com.flame.workflow.re.entity.Node;
import com.flame.workflow.ru.engine.context.RuntimeContext;
import com.flame.workflow.ru.entity.Instance;
import com.flame.workflow.ru.entity.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * 支持表达式： !=、==、>、<、=>、<=、not in、in、&&、||
 * 支持运算符： +、-、*、/ 运算符
 * 支持小括号：()
 * 输入：conditionExpressionString
 * 输出：true | false
 * <p>
 * 示例：
 * var == "aaa"
 * var > 3 && var < 5 && var == 4
 * <p>
 * var + 2 => 3
 * var in ["a","b","c"]
 * var not in [1,2,3]
 * (var == "100" && var2 > 30) || (var == "300")
 * (var == "100" && (var2 > 30 && (var2 > 30 && var2 < 30))) || (var == "300")
 * (var == "100" && (var2 >= 30 && (var2 >= 30 && var2 <= 30))) || (var == "300")
 *
 * @author wuxintong😺😺😺
 * @since 2023/6/6
 */
@Service("workflow-condition-branch-task-handler")
public class ConditionBranchTaskHandler extends AbstractTaskHandler {

    private static final Logger log = LoggerFactory.getLogger(ConditionBranchTaskHandler.class);

    @Override
    public String runTask(RuntimeContext runtimeContext) {
        Task task = runtimeContext.getTask();
        Task dbTask = taskService.getTask(task.getTaskId());

        if (RuntimeConstants.RUNTIME_STATUS_WAIT.equals(dbTask.getTaskStatus())) {
            boolean flag = true;
            List<Task> branchTaskList = taskService.listByBranchTaskId(dbTask.getTaskId());
            for (Task branchTask : branchTaskList) {
                if (!RuntimeConstants.RUNTIME_STATUS_COMPLETE.equals(branchTask.getTaskStatus())) {
                    flag = false;
                    break;
                }
            }
            if (!flag) {
                return RuntimeConstants.RUNTIME_STATUS_WAIT;
            }
            return RuntimeConstants.RUNTIME_STATUS_COMPLETE;
        } else {
            Map<String, String> properties = task.getTaskProperties();
            Instance instance = runtimeContext.getInstance();
            try {
                Map<String, Object> data =
                        objectMapper.readValue(instance.getFormData(), new TypeReference<Map<String, Object>>() {
                        });
                if (parse(properties.get("conditionExpression"), data)) {
                    List<Node> nodeList = nodeService.listByParentId(instance.getModelId(), instance.getModelVersion(), task.getNodeId());
                    if (CollectionUtils.isEmpty(nodeList)) {
                        return RuntimeConstants.RUNTIME_STATUS_COMPLETE;
                    }
                    return RuntimeConstants.RUNTIME_STATUS_WAIT;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            return RuntimeConstants.RUNTIME_STATUS_ERROR;
        }
    }

    @Override
    public boolean handleError(RuntimeContext runtimeContext) {
        Task currentTask = runtimeContext.getTask();
        List<Task> taskList = taskService.listByBranchTaskId(currentTask.getBranchTaskId());
        for (Task task : taskList) {
            if (!RuntimeConstants.RUNTIME_STATUS_ERROR.equals(task.getTaskStatus())) {
                return false;
            }
        }
        return true;
    }

    public boolean parse(String conditionExpression, Map<String, Object> data) {
        List<String> list = getStrList(conditionExpression, data);

        Stack<String> varStack = new Stack<>();
        Stack<String> opStack = new Stack<>();
        opStack.push("#");
        int i = 0;
        while (i != list.size() || !opStack.peek().equals("#")) {
            if (i == list.size()) {
                String operator = opStack.pop();
                String right = varStack.pop();
                String left = varStack.pop();
                String res = calc(left, operator, right);
                varStack.push(res);
                continue;
            }
            String str = list.get(i);
            if (isOperator(str)) {
                if (opStack.peek().equals("(") && str.equals(")")) {
                    opStack.pop();
                    i++;
                } else if (cmpOpLevel(opStack.peek(), str)) {
                    String operator = opStack.pop();
                    String right = varStack.pop();
                    String left = varStack.pop();
                    String res = calc(left, operator, right);
                    varStack.push(res);
                } else {
                    opStack.push(str);
                    i++;
                }

            } else {
                varStack.push(str);
                i++;
            }
        }
        if (opStack.peek().equals("#")) {
            return Boolean.parseBoolean(varStack.pop());
        } else {
            throw new RuntimeException("啊啊啊");
        }
    }

    private String calc(String left, String operator, String right) {
        if (isLogicOperator(operator) && isBoolean(left) && isBoolean(right)) {
            return calc(Boolean.valueOf(left), operator, Boolean.valueOf(right));
        } else if (isNumber(left) && isNumber(right)) {
            return calc(Integer.valueOf(left), operator, Integer.valueOf(right));
        } else if (isRangeOperator(operator)) {
            try {
                List<String> valueList = objectMapper.readValue(right, new TypeReference<List<String>>() {
                });
                return calc(left, operator, valueList);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return "false";
            }
        } else {
            left = removeStrMark(left);
            right = removeStrMark(right);
            if (isCompareOperator(operator)) {
                int compare = left.compareTo(right);
                if (compare == 0 && (operator.equals("==") || operator.equals("=>") || operator.equals(">=")) ||
                        operator.equals("=<") || operator.equals("<=")) {
                    return "true";
                }
                if ((compare == 1 || compare == -1) && operator.equals("!=")) {
                    return "true";
                }
                if (compare == 1 && (operator.equals("=>") || operator.equals(">=") || operator.equals(">"))) {
                    return "true";
                }
                if (compare == -1 && (operator.equals("<=") || operator.equals("=<") || operator.equals("<"))) {
                    return "true";
                }
            }
            return "false";
        }
    }

    private boolean isBoolean(String str) {
        return str.equals("true") || str.equals("false");
    }

    private String calc(String left, String operator, List<String> right) {
        if (operator.equals("in")) {
            for (String value : right) {
                value = removeStrMark(value);
                if (left.equals(value)) {
                    return "true";
                }
            }
            return "false";
        }
        if (operator.equals("not in")) {
            for (String value : right) {
                value = removeStrMark(value);
                if (left.equals(value)) {
                    return "false";
                }
            }
            return "true";
        }
        return "false";
    }

    private String calc(Boolean left, String operator, Boolean right) {
        if (operator.equals("&&")) {
            return String.valueOf(left && right);
        }
        if (operator.equals("||")) {
            return String.valueOf(left || right);
        }
        return "false";
    }

    private String calc(Integer left, String operator, Integer right) {
        if (isCompareOperator(operator)) {
            int compare = left.compareTo(right);
            if (compare == 0 && operator.contains("=")) {
                return "true";
            }
            if ((compare == 1 || compare == -1) && operator.equals("!=")) {
                return "true";
            }
            if (compare == 1 && (operator.equals("=>") || operator.equals(">=") || operator.equals(">"))) {
                return "true";
            }
            if (compare == -1 && (operator.equals("<=") || operator.equals("=<") || operator.equals("<"))) {
                return "true";
            }
            return "false";
        }
        if (operator.equals("+")) {
            return String.valueOf(left + right);
        }
        if (operator.equals("-")) {
            return String.valueOf(left - right);
        }
        if (operator.equals("*")) {
            return String.valueOf(left * right);
        }
        if (operator.equals("/")) {
            return String.valueOf(left / right);
        }
        return "false";
    }

    private boolean isNumber(String str) {
        for (char ch : str.toCharArray()) {
            if (!Character.isDigit(ch)) {
                return false;
            }
        }
        return true;
    }

    private boolean isOperator(String str) {
        // !=、==、>、<、=>、<=、>=、=<、not in、in、+、-、*、/、&&、||、(、)
        return isCompareOperator(str) || isRangeOperator(str) || isCalcOperator(str) || isLogicOperator(str) ||
                isParenthesesOperator(str);
    }

    private boolean isParenthesesOperator(String str) {
        return str.equals("(") || str.equals(")");
    }

    private boolean isCompareOperator(String str) {
        // !=、==、>、<、=>、<=、>=、=<
        return str.equals("!=") || str.equals("==") || str.equals(">") || str.equals("=>") || str.equals(">=") ||
                str.equals("<") || str.equals("<=") || str.equals("=<");
    }

    private boolean isRangeOperator(String str) {
        // not in、in
        return str.equals("not in") || str.equals("in");
    }

    private boolean isCalcOperator(String str) {
        // +、-、*、/
        return str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/");
    }


    private boolean isLogicOperator(String str) {
        // &&、||
        return str.equals("&&") || str.equals("||");
    }

    private boolean cmpOpLevel(String left, String right) {
        // #
        if (left.equals("#")) {
            return false;
        }
        // &&、||
        if (isLogicOperator(left)) {
            if (right.equals("(")) {
                return false;
            }
            if (isCompareOperator(right) || isRangeOperator(right)) {
                return false;
            }
        }
        // !=、==、>、<、=>、<=、not in、in
        if (isCompareOperator(left) || isRangeOperator(left)) {
            if (right.equals("(")) {
                return false;
            }
            if (isCalcOperator(right)) {
                return false;
            }
        }
        // +、-、*、/
        if (isCalcOperator(left)) {
            if (right.equals("(")) {
                return false;
            }
            if (left.equals("+") || left.equals("-")) {
                if (right.equals("*") || right.equals("/")) {
                    return false;
                }
            }
        }
        if (left.equals("(") && !right.equals(")")) {
            return false;
        }
        return true;
    }

    private String removeStrMark(String str) {
        if (str.startsWith("'") || str.startsWith("\"")) {
            return str.substring(1, str.length() - 1);
        }
        return str;
    }

    private List<String> getStrList(String conditionExpression, Map<String, Object> data) {
        char[] charList = conditionExpression.trim().toCharArray();
        List<String> list = new ArrayList<>();
        for (int i = 0; i < charList.length; i++) {
            if (charList[i] == ' ') {
                continue;
            } else if (charList[i] == '(') {
                list.add("(");
            } else if (charList[i] == ')') {
                list.add(")");
            } else {
                StringBuilder sb = new StringBuilder();
                int j = i;
                while (j <= charList.length) {
                    if (j == charList.length || charList[j] == ')' || charList[j] == ' ') {
                        if ("in".equals(sb.toString()) && "not".equals(list.get(list.size() - 1))) {
                            list.remove(list.size() - 1);
                            list.add("not in");
                            i = j - 1;
                            break;
                        }
                        list.add(parseVar(sb.toString(), data));
                        i = j - 1;
                        break;
                    }
                    if (j == i && (charList[j] == '&' || charList[j] == '|')) {
                        if (charList[j] == '&') {
                            list.add("&&");
                        } else {
                            list.add("||");
                        }
                        i++;
                        break;
                    } else if (j == i &&
                            (charList[j] == '!' || charList[j] == '>' || charList[j] == '=' || charList[j] == '<')) {
                        if (charList[j + 1] == '>' || charList[j + 1] == '=' || charList[j + 1] == '<') {
                            list.add("" + charList[j] + charList[j + 1]);
                            i++;
                        } else {
                            list.add("" + charList[j]);
                        }
                        break;
                    } else if (j == i &&
                            (charList[j] == '+' || charList[j] == '-' || charList[j] == '*' || charList[j] == '/')) {
                        list.add("" + charList[j]);
                        break;
                    } else if (charList[j] == '&' || charList[j] == '|' || charList[j] == '>' || charList[j] == '=' ||
                            charList[j] == '<' || charList[j] == '+' || charList[j] == '-' || charList[j] == '*' ||
                            charList[j] == '/') {
                        list.add(parseVar(sb.toString(), data));
                        i = j - 1;
                        break;
                    } else {
                        sb.append(charList[j]);
                        j++;
                    }
                }
            }
        }
        return list;
    }

    private String parseVar(String str, Map<String, Object> data) {
        if (!ObjectUtils.isEmpty(data.get(str))) {
            if (data.get(str) instanceof Integer) {
                return String.valueOf(data.get(str));
            } else {
                return (String) data.get(str);
            }
        }
        return str;
    }

    @Override
    public String taskType() {
        return TaskConstants.TASK_TYPE_CONDITION_BRANCH;
    }
}
